/*-------------------------------------------------------------------------
 *
 * brin_validate.c
 *	  BRIN 的操作类验证器。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/brin/brin_validate.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/amvalidate.h"
#include "access/brin_internal.h"
#include "access/htup_details.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/regproc.h"
#include "utils/syscache.h"

/*
 * BRIN 操作类的验证器。
 *
 * 这里进行的一些检查涵盖整个操作族，因此在检查操作族中的每个操作类时是多余的。但它们的运行时间并不长，因此我们接受这种重复，而不是让 amvalidate API 更加复杂。
 */
bool brinvalidate(Oid fc_opclassoid)
{
	bool		fc_result = true;
	HeapTuple	fc_classtup;
	Form_pg_opclass fc_classform;
	Oid			fc_opfamilyoid;
	Oid			fc_opcintype;
	char	   *fc_opclassname;
	HeapTuple	fc_familytup;
	Form_pg_opfamily fc_familyform;
	char	   *fc_opfamilyname;
	CatCList   *fc_proclist,
			   *fc_oprlist;
	uint64		fc_allfuncs = 0;
	uint64		fc_allops = 0;
	List	   *fc_grouplist;
	OpFamilyOpFuncGroup *fc_opclassgroup;
	int			fc_i;
	ListCell   *fc_lc;

	/* 获取操作类信息 */
	fc_classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclassoid));
	if (!HeapTupleIsValid(fc_classtup))
		elog(ERROR, "cache lookup failed for operator class %u", fc_opclassoid);
	fc_classform = (Form_pg_opclass) GETSTRUCT(fc_classtup);

	fc_opfamilyoid = fc_classform->opcfamily;
	fc_opcintype = fc_classform->opcintype;
	fc_opclassname = NameStr(fc_classform->opcname);

	/* 获取操作族信息 */
	fc_familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfamilyoid));
	if (!HeapTupleIsValid(fc_familytup))
		elog(ERROR, "cache lookup failed for operator family %u", fc_opfamilyoid);
	fc_familyform = (Form_pg_opfamily) GETSTRUCT(fc_familytup);

	fc_opfamilyname = NameStr(fc_familyform->opfname);

	/* 获取操作族的所有操作符和支持函数 */
	fc_oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(fc_opfamilyoid));
	fc_proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(fc_opfamilyoid));

	/* 检查单个支持函数 */
	for (fc_i = 0; fc_i < fc_proclist->n_members; fc_i++)
	{
		HeapTuple	fc_proctup = &fc_proclist->members[fc_i]->tuple;
		Form_pg_amproc fc_procform = (Form_pg_amproc) GETSTRUCT(fc_proctup);
		bool		fc_ok;

		/* 检查过程编号和函数签名 */
		switch (fc_procform->amprocnum)
		{
			case BRIN_PROCNUM_OPCINFO:
				fc_ok = check_amproc_signature(fc_procform->amproc, INTERNALOID, true,
											1, 1, INTERNALOID);
				break;
			case BRIN_PROCNUM_ADDVALUE:
				fc_ok = check_amproc_signature(fc_procform->amproc, BOOLOID, true,
											4, 4, INTERNALOID, INTERNALOID,
											INTERNALOID, INTERNALOID);
				break;
			case BRIN_PROCNUM_CONSISTENT:
				fc_ok = check_amproc_signature(fc_procform->amproc, BOOLOID, true,
											3, 4, INTERNALOID, INTERNALOID,
											INTERNALOID, INT4OID);
				break;
			case BRIN_PROCNUM_UNION:
				fc_ok = check_amproc_signature(fc_procform->amproc, BOOLOID, true,
											3, 3, INTERNALOID, INTERNALOID,
											INTERNALOID);
				break;
			case BRIN_PROCNUM_OPTIONS:
				fc_ok = check_amoptsproc_signature(fc_procform->amproc);
				break;
			default:
				/* 如果不是有效的可选过程编号，则进行投诉 */
				if (fc_procform->amprocnum < BRIN_FIRST_OPTIONAL_PROCNUM ||
					fc_procform->amprocnum > BRIN_LAST_OPTIONAL_PROCNUM)
				{
					ereport(INFO,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
									fc_opfamilyname, "brin",
									format_procedure(fc_procform->amproc),
									fc_procform->amprocnum)));
					fc_result = false;
					continue;	/* 从 allfuncs 中省略无效的过程编号 */
				}
				/* 不能检查可选过程的签名，因此假设正常 */
				fc_ok = true;
				break;
		}

		if (!fc_ok)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
							fc_opfamilyname, "brin",
							format_procedure(fc_procform->amproc),
							fc_procform->amprocnum)));
			fc_result = false;
		}

		/* 跟踪在操作族中看到的所有有效过程编号 */
		fc_allfuncs |= ((uint64) 1) << fc_procform->amprocnum;
	}

	/* 检查单个操作符 */
	for (fc_i = 0; fc_i < fc_oprlist->n_members; fc_i++)
	{
		HeapTuple	fc_oprtup = &fc_oprlist->members[fc_i]->tuple;
		Form_pg_amop fc_oprform = (Form_pg_amop) GETSTRUCT(fc_oprtup);

		/* 检查是否只存在允许的策略编号 */
		if (fc_oprform->amopstrategy < 1 || fc_oprform->amopstrategy > 63)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
							fc_opfamilyname, "brin",
							format_operator(fc_oprform->amopopr),
							fc_oprform->amopstrategy)));
			fc_result = false;
		}
		else
		{
			/*
			 * 提供的操作符集在 BRIN 操作族之间是不同的。
			 * 我们的计划是识别操作族中使用的所有操作符策略编号，然后对缺少任何操作符的数据类型组合进行投诉。
			 * 但是，仅考虑出现在某些非交叉类型案例中的编号，因为交叉类型
			 * 操作符可能有独特的策略。 （这不是一个很好的启发式，特别是在交叉类型操作符中使用的错误编号将无法被发现；但核心 BRIN 操作族非常混乱，使得这变得必要。）
			 */
			if (fc_oprform->amoplefttype == fc_oprform->amoprighttype)
				fc_allops |= ((uint64) 1) << fc_oprform->amopstrategy;
		}

		/* brin 不支持 ORDER BY 操作符 */
		if (fc_oprform->amoppurpose != AMOP_SEARCH ||
			OidIsValid(fc_oprform->amopsortfamily))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
							fc_opfamilyname, "brin",
							format_operator(fc_oprform->amopopr))));
			fc_result = false;
		}

		/* 检查操作符签名——对于所有 brin 策略都是相同的 */
		if (!check_amop_signature(fc_oprform->amopopr, BOOLOID,
								  fc_oprform->amoplefttype,
								  fc_oprform->amoprighttype))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
							fc_opfamilyname, "brin",
							format_operator(fc_oprform->amopopr))));
			fc_result = false;
		}
	}

	/* 现在检查操作符/函数组的不一致性 */
	fc_grouplist = identify_opfamily_groups(fc_oprlist, fc_proclist);
	fc_opclassgroup = NULL;
	foreach(fc_lc, fc_grouplist)
	{
		OpFamilyOpFuncGroup *fc_thisgroup = (OpFamilyOpFuncGroup *) lfirst(fc_lc);

		/* 记住与测试操作类完全匹配的组 */
		if (fc_thisgroup->lefttype == fc_opcintype &&
			fc_thisgroup->righttype == fc_opcintype)
			fc_opclassgroup = fc_thisgroup;

		/*
		 * 一些 BRIN 操作族期望存在交叉类型支持函数，而一些则不期望。
		 * 我们并不知道哪个是哪个，因此如果我们发现一个没有任何支持函数的交叉类型操作符，让其通过。
		 * （也不要指望在此类交叉类型情况下所有操作符都存在。）
		 */
		if (fc_thisgroup->functionset == 0 &&
			fc_thisgroup->lefttype != fc_thisgroup->righttype)
			continue;

		/*
		 * 否则，如果这个数据类型对的操作符或支持函数似乎有不完整的集合，则进行投诉。
		 */
		if (fc_thisgroup->operatorset != fc_allops)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
							fc_opfamilyname, "brin",
							format_type_be(fc_thisgroup->lefttype),
							format_type_be(fc_thisgroup->righttype))));
			fc_result = false;
		}
		if (fc_thisgroup->functionset != fc_allfuncs)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s is missing support function(s) for types %s and %s",
							fc_opfamilyname, "brin",
							format_type_be(fc_thisgroup->lefttype),
							format_type_be(fc_thisgroup->righttype))));
			fc_result = false;
		}
	}

	/* 检查原名操作类是否完整 */
	if (!fc_opclassgroup || fc_opclassgroup->operatorset != fc_allops)
	{
		ereport(INFO,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("operator class \"%s\" of access method %s is missing operator(s)",
						fc_opclassname, "brin")));
		fc_result = false;
	}
	for (fc_i = 1; fc_i <= BRIN_MANDATORY_NPROCS; fc_i++)
	{
		if (fc_opclassgroup &&
			(fc_opclassgroup->functionset & (((int64) 1) << fc_i)) != 0)
			continue;			/* 明白了 */
		ereport(INFO,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("operator class \"%s\" of access method %s is missing support function %d",
						fc_opclassname, "brin", fc_i)));
		fc_result = false;
	}

	ReleaseCatCacheList(fc_proclist);
	ReleaseCatCacheList(fc_oprlist);
	ReleaseSysCache(fc_familytup);
	ReleaseSysCache(fc_classtup);

	return fc_result;
}
